home *** CD-ROM | disk | FTP | other *** search
- /*
- Task Manager -- Background processing support
- version 2.2.1
-
- This software source package is Copyright ⌐ 1990-91 by Michael Hecht. All Rights
- Reserved. It may be freely distributed in source or object code format; however,
- the source code may not be sold for profit or charged for in any way. The source
- code must be distributed as a package including all H files, sample code and
- projects, and documentation.
-
- I welcome any comments or suggestions that will help me improve or extend the
- functionality of the Task Manager. You can reach me at:
-
- Internet: Michael_Hecht@mac.sas.com
- AppleLink: SAS.HECHT
-
- Happy Tasking!
-
-
- --Michael Hecht
- */
-
- #include <GestaltEqu.h>
- #include <setjmp.h>
- #include "Task.h"
-
-
- /* Private Types */
-
- typedef struct {
- jmp_buf envRegisters;
- Handle envStack;
- } TaskEnvironmentRecord;
-
- /* Register ordering within the jmp_buf */
- enum {
- d3, d4, d5, d6, d7,
- a1, a2, a3, a4, a6, a7
- };
-
- typedef struct {
- TaskProcPtr taskProc;
- TaskProcPtr taskTermProc;
- long taskRefCon;
- short taskRefNum;
- TaskEnvironmentRecord taskEnvironment;
- long taskFlags;
- } TaskRecord, *TaskPtr;
-
- /* Values for gTaskMgrFlags */
- typedef enum {
- useTempMem = 0x00000001,
- tasksRunning = 0x00000002
- } TaskMgrFlags;
-
- /* Return values from setjmp; negative values are OSErr's */
- enum {
- saveEnvironment, taskResume, taskSuspend
- };
-
- typedef struct {
- short numTasks;
- TaskRecord theTask[];
- } TaskList, **TaskListHandle;
-
- static TaskListHandle gTaskList;
- static short gTaskAtHand;
- static TaskRecord gCurrentTask;
- static short gNextTaskRefNum;
- static TaskMgrFlags gTaskMgrFlags;
- static TaskEnvironmentRecord gAppEnvironment;
- static unsigned long gTimeToStop;
-
-
-
-
- OSErr InitTasking( void )
- {
- OSErr err;
- long response, tempMask;
-
-
- /* Allocate the task list */
- gTaskList = ( TaskListHandle )NewHandle( sizeof( TaskList ));
- if( !gTaskList )
- return MemError();
-
- /* Initialize global data */
- gTaskAtHand = -1; /* Run task 0 first */
- ( *gTaskList )->numTasks = 0;
- gNextTaskRefNum = 1;
- gTaskMgrFlags = 0;
-
- /* Determine if temporary memory is available */
- err = Gestalt( gestaltOSAttr, &response );
- if( err == noErr ) {
- tempMask = ( 1 << gestaltTempMemSupport ) |
- ( 1 << gestaltRealTempMemory ) |
- ( 1 << gestaltTempMemTracked );
- if(( response & tempMask ) == tempMask )
- gTaskMgrFlags |= useTempMem;
- }
-
- return noErr;
- }
-
- OSErr TermTasking( void )
- {
- OSErr err;
- short taskIndex;
-
-
- /* Can't terminate from a task */
- if( gTaskMgrFlags & tasksRunning )
- return paramErr;
-
- /*
- * Kill all tasks. We do this from back to front because it's more
- * efficient, for two reasons:
- *
- * Ñ Less memory gets moved when we shrink the task list.
- *
- * Ñ The taskIndexes are looked up much faster when starting at
- * the end of the list.
- */
- err = noErr;
- for( taskIndex = ( *gTaskList )->numTasks - 1; taskIndex >= 0; taskIndex-- ) {
-
- err = DisposeTask(( *gTaskList )->theTask[ taskIndex ].taskRefNum );
- if( err != noErr )
- break;
- }
-
- /* Dispose of the task list */
- DisposHandle(( Handle )gTaskList );
-
- return err;
- }
-
- short CountTasks( void )
- {
- return ( *gTaskList )->numTasks;
- }
-
- short CurrentTask( void )
- {
- return ( gTaskMgrFlags & tasksRunning ) ? gCurrentTask.taskRefNum : 0;
- }
-
- short GetIndTask( short index )
- {
- return ( index < 0 || index >= ( *gTaskList )->numTasks ) ? 0 :
- ( *gTaskList )->theTask[ index ].taskRefNum;
- }
-
- static short GetTaskIndex( short taskRefNum )
- {
- short taskIndex;
-
-
- /*
- * Since taskRefNums start at 1 and always increase, we can assume that
- * the taskIndex will always be less than the taskRefNum or the number of
- * tasks, which ever is smallest. This makes a good starting point in our
- * search for the task.
- */
-
- taskIndex = ( *gTaskList )->numTasks;
- if( taskRefNum < taskIndex )
- taskIndex = taskRefNum;
- taskIndex--;
-
- for( ; taskIndex >= 0; taskIndex-- ) {
- if(( *gTaskList )->theTask[ taskIndex ].taskRefNum == taskRefNum )
- break;
- }
-
- /* Note that a negative value will be returned if the task isn't found */
- return taskIndex;
- }
-
- long GetTaskRefCon( short taskRefNum )
- {
- short taskIndex;
-
-
- taskIndex = GetTaskIndex( taskRefNum );
- if( taskIndex < 0 )
- return 0;
-
- return ( *gTaskList )->theTask[ taskIndex ].taskRefCon;
- }
-
- OSErr SetTaskRefCon( short taskRefNum, long taskRefCon )
- {
- short taskIndex;
-
-
- taskIndex = GetTaskIndex( taskRefNum );
- if( taskIndex < 0 )
- return paramErr;
-
- ( *gTaskList )->theTask[ taskIndex ].taskRefCon = taskRefCon;
- return noErr;
- }
-
- static OSErr AllocStack( TaskEnvironmentRecord *theEnvironment, Size stackSize )
- {
- /* Has the stack already been allocated? */
- if( theEnvironment->envStack ) {
-
- /* Try to reallocate it */
- ReallocHandle( theEnvironment->envStack, stackSize );
- if( MemError() != noErr )
- return noErr;
-
- /* Dispose of the stack and try allocating a whole new one */
- DisposHandle( theEnvironment->envStack );
- }
-
- /* Try temporary memory first */
- if( gTaskMgrFlags & useTempMem ) {
- OSErr err;
-
-
- theEnvironment->envStack = TempNewHandle( stackSize, &err );
- if( err == noErr )
- return noErr;
- }
-
- /* If that didn't work, try allocating from the heap */
- theEnvironment->envStack = NewHandle( stackSize );
- return MemError();
- }
-
- /*
- * The register declarations in the following routine are quite necessary. The
- * setjmp call causes THINK C to disable automatic optimizations, so we must
- * explicitly ask that these variables be placed in registers. It is imperative
- * that we do so, because these variables cannot survive the other side of the
- * setjmp until after the stack is restored.
- */
- static OSErr SaveEnvironment( register TaskEnvironmentRecord *theEnvironment )
- {
- OSErr err;
- register OSErr status;
- Ptr stackPtr;
- register Size stackSize;
-
-
- /* Save the registers */
- status = ( OSErr )setjmp( theEnvironment->envRegisters );
- if( status ) {
-
- /* Restore the stack */
- stackSize = GetHandleSize( theEnvironment->envStack );
- BlockMove( *theEnvironment->envStack, CurStackBase - stackSize, stackSize );
- HPurge( theEnvironment->envStack );
- return status;
- }
-
- /* Allocate the stack */
- stackPtr = ( Ptr )theEnvironment->envRegisters[ a7 ];
- stackSize = CurStackBase - stackPtr;
- err = AllocStack( theEnvironment, stackSize );
- if( err != noErr )
- return err;
-
- /* Save the stack */
- HNoPurge( theEnvironment->envStack );
- BlockMove( stackPtr, *theEnvironment->envStack, stackSize );
- return noErr;
- }
-
- static void RestoreEnvironment( TaskEnvironmentRecord *theEnvironment, OSErr status )
- {
- /* Can't let the stack cross into the heap! */
- if(( Ptr )theEnvironment->envRegisters[ a7 ] < HeapEnd ) {
- #if TASK_DEBUG
- DebugStr( "\pTaskMgr: Stack overflow" );
- ExitToShell();
- #else
- SysError( 28 );
- #endif
- }
-
- #if TASK_DEBUG
- /* Look for windows in the stack and warn about them */
- {
- WindowPeek peek;
- Str255 msg, title;
-
-
- for( peek = WindowList; peek; peek = peek->nextWindow ) {
- if(( Ptr )peek < HeapEnd )
- continue;
-
- GetWTitle(( WindowPtr )peek, title );
- BlockMove( "\pTaskMgr: Window in stack: ", msg, 27 );
- BlockMove( title + 1, msg + msg[ 0 ] + 1, title[ 0 ]);
- msg[ 0 ] += title[ 0 ];
- DebugStr( msg );
- ExitToShell();
- }
- }
- #endif
-
- /* Restore the registers */
- longjmp( theEnvironment->envRegisters, ( int )status );
- }
-
- static void StartNextTask( void )
- {
- /* Move to next task at hand */
- gTaskAtHand++;
- if( gTaskAtHand >= ( *gTaskList )->numTasks )
- gTaskAtHand = 0;
-
- /* Keep gCurrentTask up-to-date */
- gCurrentTask = ( *gTaskList )->theTask[ gTaskAtHand ];
-
- /* Start the next task */
- RestoreEnvironment( &gCurrentTask.taskEnvironment, taskResume );
-
- #if TASK_DEBUG
- /* This statement should never be hit */
- DebugStr( "\pTaskMgr/StartNextTask: returned from RestoreEnvironment!?!?" );
- #endif
- }
-
- OSErr RunTasks( unsigned long wakeTime )
- {
- OSErr status;
-
-
- #if TASK_DEBUG
- /* Called from task? */
- if( gTaskMgrFlags & tasksRunning ) {
- DebugStr( "\pTaskMgr/RunTasks: Called from task" );
- return paramErr;
- }
- #endif
-
- /* Nothing to do if no tasks to run */
- if(( *gTaskList )->numTasks == 0 )
- return noErr;
-
- /* Determine when to stop running tasks */
- gTimeToStop = TickCount();
- gTimeToStop += wakeTime;
-
- /* Save application's state */
- status = SaveEnvironment( &gAppEnvironment );
- switch( status ) {
-
- case saveEnvironment:
- /* We just saved the application's environment; time to start next task */
- StartNextTask();
-
- /* No break needed here because StartNextTask never returns */
-
- case taskSuspend:
- /* Tasks have suspended execution; time to return to the application */
- status = noErr;
- break;
-
- default:
- /* Anything else is an OSErr code */
-
- /* This case will be hit if SaveEnvironment couldn't */
- #if TASK_DEBUG
- DebugStr( "\pTaskMgr/RunTasks: Can't save environment" );
- #endif
- break;
- }
-
- return status;
- }
-
- OSErr TaskYield( void )
- {
- short taskAtHand;
- OSErr status;
- Boolean timeToSuspend;
- EventRecord theEvent;
-
-
- #if TASK_DEBUG
- /* Called from application? */
- if( !( gTaskMgrFlags & tasksRunning )) {
- DebugStr( "\pTaskMgr/TaskYield: Called from application" );
- return paramErr;
- }
- #endif
-
- /*
- * Determine if it's time to return to the application.
- *
- * It's that time if the wake time has run out or if the application
- * received an event.
- */
- timeToSuspend = ( TickCount() >= gTimeToStop ) ||
- ( EventAvail( everyEvent, &theEvent ));
-
- /* if it's not time to suspend and I'm the only task, then I'll just keep running */
- if( !timeToSuspend && ( *gTaskList )->numTasks == 1 )
- return noErr;
-
- /* Save the current task's environment */
- status = SaveEnvironment( &gCurrentTask.taskEnvironment );
-
- /* Return to the task */
- if( status != saveEnvironment ) {
-
- if( status > noErr )
- status = noErr;
- #if TASK_DEBUG
- /* A negative status is an OSErr */
- else
- DebugStr( "\pTaskMgr/TaskYield: Can't save environment" );
- #endif
-
- /* Tasks are running now */
- gTaskMgrFlags |= tasksRunning;
-
- return status;
- }
-
- /* Put the saved environment in the task list */
- ( *gTaskList )->theTask[ gTaskAtHand ].taskEnvironment =
- gCurrentTask.taskEnvironment;
-
- /* If it's time to return to the application, then do so */
- if( timeToSuspend ) {
-
- /* Tasks no longer running */
- gTaskMgrFlags &= ~tasksRunning;
-
- /* Return to the application */
- RestoreEnvironment( &gAppEnvironment, taskSuspend );
- }
-
- /* Start the next task */
- StartNextTask();
- }
-
- OSErr DisposeTask( short taskRefNum )
- {
- short taskIndex;
- TaskRecord dyingTask;
- Boolean harakiri;
-
-
- taskIndex = GetTaskIndex( taskRefNum );
- if( taskIndex < 0 )
- return paramErr;
-
- /* Are we deleting the current task? */
- harakiri = gTaskMgrFlags & tasksRunning && taskIndex == gTaskAtHand;
-
- /* Point to the task record of the task we're disposing */
- dyingTask = ( *gTaskList )->theTask[ taskIndex ];
-
- /* If the task has a term proc, call it now */
- if( dyingTask.taskTermProc )
- ( *dyingTask.taskTermProc )( dyingTask.taskRefCon );
-
- /* We can dispose of its stack now */
- DisposHandle( dyingTask.taskEnvironment.envStack );
-
- /* Remove the task from the task list */
- Munger(( Handle )gTaskList, sizeof( TaskList ) + taskIndex * sizeof( TaskRecord ),
- 0, sizeof( TaskRecord ), "", 0 );
-
- /* Fix up task at hand if necessary */
- if( gTaskAtHand >= taskIndex ) {
- --gTaskAtHand;
- if( gTaskAtHand < 0 )
- gTaskAtHand = ( *gTaskList )->numTasks;
- }
-
- /* One less task to keep track of */
- --( *gTaskList )->numTasks;
-
- /* Return to the application if we deleted ourselves */
- if( harakiri ) {
-
- /* Tasks are no longer running (we will be returning to the application) */
- gTaskMgrFlags &= ~tasksRunning;
- RestoreEnvironment( &gAppEnvironment, taskSuspend );
- }
-
- /* Not disposing of ourselves; return to the caller */
- return noErr;
- }
-
- static void TaskLife( void )
- {
- /* TaskLife is the task's life cycle */
-
- /* We are now running a task */
- gTaskMgrFlags |= tasksRunning;
-
- /* Call the task procedure */
- ( *gCurrentTask.taskProc )( gCurrentTask.taskRefCon );
-
- /* Delete the task */
- DisposeTask( gCurrentTask.taskRefNum );
- }
-
- OSErr NewTask( TaskProcPtr taskProc, TaskProcPtr taskTermProc,
- long taskRefCon, short *taskRefNum )
- {
- OSErr err;
- OSErr status;
- TaskRecord saveTask;
-
-
- /* Make a backup copy of the current task */
- saveTask = gCurrentTask;
-
- /* Initialize the task record */
- gCurrentTask.taskProc = taskProc;
- gCurrentTask.taskTermProc = taskTermProc;
- gCurrentTask.taskRefCon = taskRefCon;
- gCurrentTask.taskRefNum = gNextTaskRefNum++;
- gCurrentTask.taskFlags = 0;
- gCurrentTask.taskEnvironment.envStack = 0;
-
- /* Give task refNum back to caller */
- if( taskRefNum )
- *taskRefNum = gCurrentTask.taskRefNum;
-
- /* Initialize the task's environment */
- status = SaveEnvironment( &gCurrentTask.taskEnvironment );
- if( status < noErr ) {
- err = status;
- goto exitNewTask;
- }
-
- if( status > saveEnvironment ) {
- TaskLife();
- /* Never to return╔ */
- }
-
- /* Add task to task list */
- err = PtrAndHand( &gCurrentTask, ( Handle )gTaskList, sizeof( TaskRecord ));
- if( err != noErr ) {
-
- /* Dispose of the stack */
- DisposHandle( gCurrentTask.taskEnvironment.envStack );
-
- /* Not enough memory to add it to the task list */
- goto exitNewTask;
- }
- ( *gTaskList )->numTasks++;
-
- /* All dressed up and nowhere to go */
- err = noErr;
-
- exitNewTask:
- gCurrentTask = saveTask;
- return err;
- }
-